package p05_BeanFactory后处理器;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;

import java.io.IOException;
import java.util.Set;

/**
 * BeanFactory后处理器ConfigurationClassPostProcessor是如何解析@Bean的呢?
 * 原理跟@ComponentScan注解解析差不多
 * ClassPathResource可以直接得到类路径下的资源
 */
public class _3_解析Bean注解 {
    public static void main(String[] args) throws IOException {

        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(MyBean.class);
        context.refresh();
        for (String name : context.getBeanDefinitionNames()) {
            System.out.println(name);
        }
    }

    static class MyBean implements BeanFactoryPostProcessor {
        /**
         * 这里面的配置类写死了，spring会获取所有配置类，然后逐个进行以下解析
         * 所以我猜ConfigurationClassPostProcessor会先解析@Configuration,得到所有配置类后，在解析后续的注解
         */
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            try {
                GenericApplicationContext context = new GenericApplicationContext((DefaultListableBeanFactory) beanFactory);
                //解析@Bean时，要保证所在的Config类被加入容器，因为@Bean会把Config类当做工厂来创建bean
                context.registerBean("config", Config.class);

                CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
                //解析这个类中带有@Bean注解的方法(factory.getMetadataReader不走类加载，效率也比反射高)
                MetadataReader reader = factory.getMetadataReader(new ClassPathResource("p05_BeanFactory后处理器/Config.class"));
                //得到该配置类中所有被@Bean标注的方法信息
                Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
                for (MethodMetadata method : methods) {
                    //得到该方法Bean直接的initMethod属性值
                    String initMethod = method.getAnnotationAttributes(Bean.class.getName()).get("initMethod").toString();

                    //下面代码都是构建一个bean定义
                    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
                    /**
                     * Config类本质就是一个工厂，通过反射得到该工厂所有加了@Bean注解的方法，然后调用这些方法，将返回值放入容器
                     * 这就是@Bean的原理，所以构建bean定义时需要给bean定义指定工厂，将来构造bean时就去调用这个工厂的指定方法，而不是根据全类名创建bean(这种bean也没有指定全类名)
                     */
                    builder.setFactoryMethodOnBean(method.getMethodName(), "config");
                    //这种模式下，如果构造方法，或者工厂方法有参数，会根据参数类型自动在容器中找bean
                    builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
                    if (initMethod.length() > 0) {
                        //配置初始化方法名字(创建bean时会去调用指定方法)
                        builder.setInitMethodName(initMethod);
                        //其他配置雷同
                    }
                    AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();

                    //注册bean定义
                    context.registerBeanDefinition(method.getMethodName(), beanDefinition);
                }
            } catch (Exception e) {

            }
        }

    }

}
